#include "vanila/baseobject.h"
#include "vanila/dsobject.h"
#include "vanila/object.h"
#include "vanila/garbagecollector.h"
#include "vanila/virtualmachine.h"
#include "vanila/compiler.h"
#include "vanila/common.h"
#include "vanila/environment.h"
#include "vanila/allocator.h"
#include "utils/format.h"
#include <iostream>
#include <string>
#include <vector>

namespace vanila
{
static VirtualMachine* vm = utils::Singleton<VirtualMachine>::instance();
static Compiler* compiler = utils::Singleton<Compiler>::instance();
static Allocator* allocator = utils::Singleton<Allocator>::instance();

GarbageCollector::GarbageCollector() noexcept:
    _grayStack{}
{}

#define DEBUG_LOG_GC
//! \brief release the object instance which never been used
void GarbageCollector::collectGarbage()
{
#ifdef DEBUG_LOG_GC
    std::cout << "-- gc begin\n";
#endif

    this->markRoots();

    this->traceReferences();

    this->removeWhiteStrings();

    this->sweep();

#ifdef DEBUG_LOG_GC
    std::cout << "-- gc end\n";
#endif
}

//! \brief mark the root object as reacheable
void GarbageCollector::markRoots()
{
    // mark slot's object
    for (Value* slot = vm->stack(); slot < vm->stackTop(); slot++)
        this->markValue(slot);
    
    // mark frame's upvalue
    for (auto iter = vm->frames().begin(); iter != vm->frames().end(); ++iter)
        this->markObject(iter->closure);

    // mark open values
    for (ObjectUpvalue* upvalue = vm->openUpvalues(); upvalue != nullptr; upvalue = upvalue->next())
        this->markObject(upvalue);

    // mark global variables
    this->markTable(vm->globals());

    // mark compiler's roots
    Environment* enviroment = compiler->currentEnvironment();
    while (enviroment != nullptr)
    {
        this->markObject(enviroment->function());
        enviroment = enviroment->enclosing();
    }

    // mark vm's stable string
    this->markObject(ObjectClass::initMethodName);
    this->markObject(ObjectClass::strMethodName);
    this->markObject(ObjectClass::hashMethodName);
    this->markObject(ObjectClass::equalMethodName);
    this->markObject(ObjectClass::lessMethodName);
    this->markObject(ObjectClass::greaterMethodName);
}

//! \brief mark a value
void GarbageCollector::markValue(Value* slot)
{
    if (slot->isObject())
        this->markObject(slot->object());
}

//! \brief mark a value
void GarbageCollector::markValue(const Value& slot)
{
    if (slot.isObject())
        this->markObject(slot.object());
}

//! \brief mark a object
void GarbageCollector::markObject(Object* object)
{
    if (object == nullptr || object->isMarked())
        return;
#ifdef DEBUG_LOG_GC
    std::cout << utils::format("%p mark ", object);
    object->print();
    std::cout << '\n';
#endif
    object->mark();
    this->_grayStack.push(object);
}

//! \brief mark values
void GarbageCollector::markArray(std::vector<Value>& array)
{
    for (size_t i = 0; i < array.size(); ++i)
        this->markValue(array[i]);
}

//! \brief mark table
void GarbageCollector::markTable(std::map<ObjectString*, Value>& table)
{
    for (auto iter = table.begin(); iter != table.end(); ++iter)
    {
        this->markObject(iter->first);
        this->markValue(&(iter->second));
    }
}

//! \brief 
void GarbageCollector::traceReferences()
{
    while (this->_grayStack.size() > 0)
    {
        Object* object = this->_grayStack.top();
        this->_grayStack.pop();
        this->blackenObject(object);
    }
}

//! \brief set a object to black
void GarbageCollector::blackenObject(Object* object)
{
#ifdef DEBUG_LOG_GC
    std::cout << utils::format("%p blacken ", object);
    object->print();
    std::cout << '\n';
#endif

    switch (object->type())
    {
    case ObjectType::BOUND_METHOD:
    {
        ObjectBoundMethod* bound = object->asBoundMethod();
        this->markValue(bound->recevier());
        this->markObject(bound->method());
        break;
    }
    case ObjectType::CLASS:
    {
        ObjectClass* klass = object->asClass();
        this->markObject(klass->name());
        this->markTable(klass->methods());
        break;
    }
    case ObjectType::CLOSURE:
    {
        ObjectClosure* closure = object->asClosure();
        this->markObject(closure->function());
        for (ObjectUpvalue* upvalue : closure->upvalues())
            this->markObject(upvalue);
        break;
    }
    case ObjectType::DICTIONARY:
    {
        ObjectDictionary* dictionary = object->asDictionary();
        for (auto& pair : dictionary->dict())
        {
            this->markValue(pair.first);
            this->markValue(pair.second);
        }
        break;
    }
    case ObjectType::FUNCTION:
    {
        ObjectFunction* function = object->asFunction();
        this->markObject(function->name());
        this->markArray(function->chunk().constants());
        break;
    }
    case ObjectType::INSTANCE:
    {
        ObjectInstance* instance = object->asInstance();
        this->markObject(instance->klass());

        if (instance->isNative())
            this->markObject(instance->object());
        else
            this->markTable(instance->fields());
            
        break;
    }
    case ObjectType::LIST:
    {
        ObjectList* listObject = object->asList();
        std::vector<Value> list = listObject->list();
        for (Value& value : list)
            this->markValue(value);
        break;
    }
    case ObjectType::UPVALUE:
    {
        this->markValue(object->asUpvalue()->closed());
        break;
    }
    case ObjectType::NATIVE:
    case ObjectType::STRING:
        break;
    }
}

//! \brief release all white object
void GarbageCollector::sweep()
{
    using Iterator = std::list<Object*>::iterator;
    Iterator previous = vm->objects().end();
    Iterator current = vm->objects().begin();

    while (current != vm->objects().end())
    {
        if ( (*current)->isMarked() )
        {
            (*current)->unmark();
            previous = current;
            current++;
        }
        else
        {
            Object* unreadched = *current;
            vm->objects().erase(current++);
            allocator->deallocateObject(unreadched);
        }
    }
}

//! \brief remove the white string in string table
void GarbageCollector::removeWhiteStrings()
{
    for (auto iter = vm->strings().begin(); iter != vm->strings().end();)
    {
        auto temp = iter;
        iter++;

        if (!temp->second->isMarked())
            vm->strings().erase(temp);
    }
}

}